package kbl.language;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;
import java.util.Map.Entry;

import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.JPanel;

import kbl.language.DFAWalker;
import kbl.language.StrategyNFA2DFA;
import kbl.language.testDFAStateWrapper;


import org.apache.commons.collections15.Transformer;

import edu.uci.ics.jung.algorithms.layout.CircleLayout;
import edu.uci.ics.jung.algorithms.layout.FRLayout;
import edu.uci.ics.jung.algorithms.layout.ISOMLayout;
import edu.uci.ics.jung.algorithms.layout.KKLayout;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.SpringLayout;
import edu.uci.ics.jung.algorithms.layout.SpringLayout2;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.SparseMultigraph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer;
import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.layout.LayoutTransition;
import edu.uci.ics.jung.visualization.renderers.EdgeLabelRenderer;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import edu.uci.ics.jung.visualization.renderers.VertexLabelRenderer;
import edu.uci.ics.jung.visualization.subLayout.GraphCollapser;
import edu.uci.ics.jung.visualization.util.Animator;

public class DFAdrawer extends JApplet {

	private static final long serialVersionUID = 1526716590965776504L;
	Layout<Vertex, Edge> layout;
	VisualizationViewer<Vertex, Edge> viewer;
	Graph<Vertex, Edge> graph;
	Graph<Vertex, Edge> collapsedGraph;
	GraphCollapser collapser;
	VertexLabelRenderer vertexLabelRenderer;
	EdgeLabelRenderer edgeLabelRenderer;
	final Set<Edge> exclusions = new HashSet<Edge>();

	private DFAWalker it;

	public DFAdrawer(StrategyNFA2DFA dfa) {
		this.it = (DFAWalker) dfa.getIterator();
		draw();
	}

	public void clearState() {
		it.clearState();
		draw();
	}

	public void draw() {
		graph = new SparseMultigraph<Vertex, Edge>();
		/*
		 * Vertex v1 = new Vertex(1); Vertex v2 = new Vertex(2); Vertex v3 = new
		 * Vertex(3); Vertex v4 = new Vertex(4); graph.addVertex(v1);
		 * graph.addVertex(v2); graph.addVertex(v3); graph.addVertex(v4);
		 * graph.addEdge(new Edge("edge", v1, v2), v1, v2, EdgeType.DIRECTED);
		 * graph.addEdge(new Edge("edge", v2, v1), v2, v1, EdgeType.DIRECTED);
		 * graph.addEdge(new Edge("edge", v2, v3), v2, v3, EdgeType.DIRECTED);
		 * graph.addEdge(new Edge("edge", v4, v4), v1, v4, EdgeType.DIRECTED);
		 */

		while (it.hasNext()) {
			testDFAStateWrapper subgraph = it.next();
			Vertex from = new Vertex(subgraph.id, subgraph.match, subgraph.name);
			graph.addVertex(from);
			for (Entry<String, testDFAStateWrapper> entry : subgraph.cc2DFAMap
					.entrySet()) {
				Vertex to = new Vertex(entry.getValue().id, entry
						.getValue().match, entry.getValue().name);
				graph.addVertex(to);
				graph.addEdge(new Edge(entry.getKey().toString(), from, to),
						from, to, EdgeType.DIRECTED);
			}
		}

		collapsedGraph = graph;
		collapser = new GraphCollapser(graph);

		layout = new FRLayout<Vertex, Edge>(graph);
		viewer = new VisualizationViewer<Vertex, Edge>(layout, new Dimension(
				600, 400));
		viewer.setBackground(Color.white);

		viewer.getRenderer().getVertexLabelRenderer()
				.setPosition(Renderer.VertexLabel.Position.CNTR);
		viewer.getRenderContext().setVertexLabelTransformer(
				new ToStringLabeller<Vertex>());
		viewer.getRenderContext().setEdgeLabelTransformer(
				new ToStringLabeller<Edge>());

		/*
		 * Transformer<Edge, String> stringer = new Transformer<Edge, String>()
		 * { public String transform(Edge e) { return "Edge:" +
		 * graph.getEndpoints(e).toString(); } };
		 * 
		 * vv.getRenderContext().setEdgeLabelTransformer(stringer);
		 */

		viewer.getRenderContext().setVertexShapeTransformer(
				new ClusterVertexShapeFunction<Vertex>());

		viewer.getRenderContext().setEdgeDrawPaintTransformer(
				new PickableEdgePaintTransformer<Edge>(viewer
						.getPickedEdgeState(), Color.black, Color.cyan));
		viewer.getRenderContext().setVertexFillPaintTransformer(
				new PickableVertexPaintTransformer<Vertex>(viewer
						.getPickedVertexState(), Color.red, Color.yellow));

		// create a form to hold the graph
		final GraphZoomScrollPane panel = new GraphZoomScrollPane(viewer);
		Container content = getContentPane();
		content.add(panel);

		final DefaultModalGraphMouse<Integer, Number> graphMouse = new DefaultModalGraphMouse<Integer, Number>();
		viewer.setGraphMouse(graphMouse);
		graphMouse.setMode(ModalGraphMouse.Mode.PICKING);

		initialGUI(content, graphMouse);
	}

	private void initialGUI(Container content,
			final DefaultModalGraphMouse<Integer, Number> graphMouse) {
		Vector<Class<? extends Layout<Vertex, Edge>>> combos = getCombos();
		final JComboBox jcb = new JComboBox(combos);
		// use a renderer to shorten the layout name presentation
		jcb.setRenderer(new DefaultListCellRenderer() {
			private static final long serialVersionUID = 2886015296102490146L;

			public Component getListCellRendererComponent(JList list,
					Object value, int index, boolean isSelected,
					boolean cellHasFocus) {
				String valueString = value.toString();
				valueString = valueString.substring(valueString
						.lastIndexOf('.') + 1);
				return super.getListCellRendererComponent(list, valueString,
						index, isSelected, cellHasFocus);
			}
		});
		jcb.addActionListener(new LayoutChooser(jcb, viewer));
		jcb.setSelectedItem(CircleLayout.class);

		JButton collapse = new JButton("Collapse");
		collapse.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				Collection<Vertex> picked = new HashSet<Vertex>(viewer
						.getPickedVertexState().getPicked());
				if (picked.size() > 1) {
					Graph<Vertex, Edge> inGraph = layout.getGraph();
					@SuppressWarnings("unchecked")
					Graph<Vertex, Edge> clusterGraph = collapser
							.getClusterGraph(inGraph, picked);

					@SuppressWarnings("unchecked")
					Graph<Vertex, Edge> g = collapser.collapse(
							layout.getGraph(), clusterGraph);
					collapsedGraph = g;
					double sumx = 0;
					double sumy = 0;
					for (Object v : picked) {
						if (v instanceof Vertex) {
							Point2D p = (Point2D) layout.transform((Vertex) v);
							sumx += p.getX();
							sumy += p.getY();
						}
					}
					Point2D cp = new Point2D.Double(sumx / picked.size(), sumy
							/ picked.size());
					viewer.getRenderContext().getParallelEdgeIndexFunction()
							.reset();
					layout.setGraph(g);
					layout.setLocation(null, cp);
					viewer.getPickedVertexState().clear();
					viewer.repaint();
				}
			}
		});

		JButton expand = new JButton("Expand");
		expand.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				Collection<Vertex> picked = new HashSet<Vertex>(viewer
						.getPickedVertexState().getPicked());
				for (Object v : picked) {
					if (v instanceof Graph) {

						@SuppressWarnings("unchecked")
						Graph<Vertex, Edge> g = collapser.expand(
								layout.getGraph(), (Graph<?, ?>) v);
						viewer.getRenderContext()
								.getParallelEdgeIndexFunction().reset();
						layout.setGraph(g);
					}
					viewer.getPickedVertexState().clear();
					viewer.repaint();
				}
			}
		});

		JButton reset = new JButton("Reset");
		reset.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				clearState();
				layout.setGraph(graph);
				exclusions.clear();
				viewer.repaint();
			}
		});
		JPanel collapseControls = new JPanel(new GridLayout(3, 1));
		collapseControls.setBorder(BorderFactory.createTitledBorder("Picked"));
		collapseControls.add(collapse);
		collapseControls.add(expand);
		collapseControls.add(reset);

		JPanel modePanel = new JPanel(new GridLayout(2, 1));
		modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
		modePanel.add(graphMouse.getModeComboBox());

		JPanel controls = new JPanel();
		controls.add(modePanel);
		controls.add(jcb);
		controls.add(collapseControls);
		content.add(controls, BorderLayout.SOUTH);
	}

	class ClusterVertexShapeFunction<V> extends
			EllipseVertexShapeTransformer<V> {

		ClusterVertexShapeFunction() {
			setSizeTransformer(new ClusterVertexSizeFunction<V>(20));
		}

		@Override
		public Shape transform(V v) {
			if (v instanceof Graph) {
				@SuppressWarnings("unchecked")
				int size = ((Graph<Vertex, Edge>) v).getVertexCount();
				if (size < 8) {
					int sides = Math.max(size, 3);
					return factory.getRegularPolygon(v, sides);
				} else {
					return factory.getRegularStar(v, size);
				}
			}
			return super.transform(v);
		}
	}

	class ClusterVertexSizeFunction<V> implements Transformer<V, Integer> {
		int size;

		public ClusterVertexSizeFunction(Integer size) {
			this.size = size;
		}

		public Integer transform(V v) {
			if (v instanceof Graph) {
				return 30;
			}
			return size;
		}
	}

	private class LayoutChooser implements ActionListener {
		private final JComboBox jcb;
		private final VisualizationViewer<Vertex, Edge> vv;

		private LayoutChooser(JComboBox jcb,
				VisualizationViewer<Vertex, Edge> vv2) {
			super();
			this.jcb = jcb;
			this.vv = vv2;
		}

		public void actionPerformed(ActionEvent arg0) {
			Object[] constructorArgs = { graph };

			@SuppressWarnings("unchecked")
			Class<? extends Layout<Integer, Number>> layoutC = (Class<? extends Layout<Integer, Number>>) jcb
					.getSelectedItem();

			try {
				Constructor<? extends Layout<Integer, Number>> constructor = layoutC
						.getConstructor(new Class[] { Graph.class });
				Object o = constructor.newInstance(constructorArgs);
				@SuppressWarnings("unchecked")
				Layout<Vertex, Edge> l = (Layout<Vertex, Edge>) o;
				l.setInitializer(vv.getGraphLayout());
				l.setSize(vv.getSize());
				layout = l;
				LayoutTransition<Vertex, Edge> lt = new LayoutTransition<Vertex, Edge>(
						vv, vv.getGraphLayout(), l);
				Animator animator = new Animator(lt);
				animator.start();
				vv.getRenderContext().getMultiLayerTransformer()
						.setToIdentity();
				vv.repaint();

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	@SuppressWarnings("unchecked")
	private Vector<Class<? extends Layout<Vertex, Edge>>> getCombos() {
		Vector<Class<? extends Layout<Vertex, Edge>>> layouts = new Vector<Class<? extends Layout<Vertex, Edge>>>();
		layouts.add((Class<? extends Layout<Vertex, Edge>>) KKLayout.class);
		layouts.add((Class<? extends Layout<Vertex, Edge>>) FRLayout.class);
		layouts.add((Class<? extends Layout<Vertex, Edge>>) CircleLayout.class);
		layouts.add((Class<? extends Layout<Vertex, Edge>>) SpringLayout.class);
		layouts.add((Class<? extends Layout<Vertex, Edge>>) SpringLayout2.class);
		layouts.add((Class<? extends Layout<Vertex, Edge>>) ISOMLayout.class);
		return layouts;
	}

	static class Vertex {
		int id;
		String name;
		boolean match;

		Vertex(int id) {
			this.id = id;
		}

		Vertex(int id, boolean match, String name) {
			this.id = id;
			this.match = match;
			this.name = name;
		}

		@Override
		public String toString() {
			if (match)
				//return Integer.toString(id) + '*';
				return '*'+this.name;
			else
				//return Integer.toString(id);
				return this.name;
		}

		@Override
		public int hashCode() {
			return id;
		}

		@Override
		public boolean equals(Object other) {
			if (other instanceof Vertex) {
				Vertex that = (Vertex) other;
				return this.id == that.id;
			}
			return false;
		}
	}

	static class Edge {
		String id;
		String desc;

		Edge(String st, Vertex from, Vertex to) {
			this.desc = st;
			this.id = Integer.toString(from.id) + '-' + Integer.toString(to.id);
		}

		@Override
		public String toString() {
			return desc;
		}

		@Override
		public int hashCode() {
			return id.hashCode();
		}

		@Override
		public boolean equals(Object other) {
			if (other instanceof Edge) {
				Edge that = (Edge) other;
				return this.id.equals(that.id);
			}
			return false;
		}
	}
}
